/**
 * 
 */
package com.googlecode.cswish.struts.interceptor;

import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.interceptor.ServletResponseAware;
import org.hibernate.HibernateException;

import com.googlecode.cswish.exception.ApplicationException;
import com.googlecode.cswish.exception.ApplicationMessage;
import com.googlecode.cswish.struts.Action;
import com.googlecode.cswish.struts.spring.SystemEnv;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.TextProvider;
import com.opensymphony.xwork2.ValidationAware;
import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.ExceptionHolder;
import com.opensymphony.xwork2.util.CompoundRoot;
import com.opensymphony.xwork2.util.ValueStack;

/**
 * Convert DB Exception to error message
 * 
 * @author Jerry.Feng Date: 2008-3-19
 *
 */
public class ExceptionMappingInterceptor extends 
	com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor implements ServletResponseAware {
	
	private static final long serialVersionUID = -2630928516871361600L;

	protected static final Log logger = LogFactory.getLog(ExceptionMappingInterceptor.class);

	protected boolean devMode;
	
	public static String interceptorEx;
	
    @Inject(StrutsConstants.STRUTS_DEVMODE)
    public void setDevMode(String mode) {
    	this.devMode = "true".equals(System.getProperty(SystemEnv.RUNNING_IN_ECLIPSE));
    }
	
	public String intercept(ActionInvocation invocation) throws Exception {
		String result;

        try {
        	if (interceptorEx != null) {
        		throw new ApplicationException(interceptorEx);
        	}
        	
        	result = invocation.invoke();
        } catch (Exception ex) {
        	if (ex instanceof InvocationTargetException) {
        		InvocationTargetException invocationEx = (InvocationTargetException) ex;
        		if (invocationEx.getTargetException() instanceof Exception) {
        			ex = (Exception) invocationEx.getTargetException();
        		}
        	}
        	
        	if (ex instanceof ApplicationException) {
        		if (this.devMode) {
        			logger.debug("Application Exception", ex);
        		}
        		
        		ApplicationException appEx = (ApplicationException) ex;
        		Locale locale = ActionContext.getContext().getLocale();
        		String message = getText(invocation, appEx.getMessageKey(), locale, appEx.getArguments());
        		
        		if (ex instanceof ApplicationMessage) {
        			addActionMessage(invocation, message);
        		} else {
        			addActionError(invocation, message);
        		}
        		
        		result = Action.ERROR;
        	} else {
        		logger.error("Find system exception", ex);
        		
	        	result = process(invocation, ex);
	        	if (result == null) {
	        		result = callOrigExceptionInterceptor(invocation, ex);
	        	}
        	}
        }
        
        return result;
	}
	
	
	public String process(ActionInvocation invocation, Exception ex) {
		Throwable cause = ex;
		while (cause.getCause() != null) {
			cause = cause.getCause();
		}
		
		String status = null;
		String message = null;
		if (cause instanceof SQLException) {
			SQLException sqlEx = (SQLException) cause;
			status = sqlEx.getSQLState();
			message = sqlEx.getMessage();
		} else if (cause instanceof HibernateException) {
			HibernateException hbmEx = (HibernateException) cause;
			message = hbmEx.getMessage();
		} else {
			message = cause.getMessage();
		}
		
		Locale locale = ActionContext.getContext().getLocale();
		if (status != null) {
			if (message != null && message.startsWith("Duplicate entry '")) {
				int valueBegin = "Duplicate entry '".length();
				int valueEnd = message.indexOf('\'', valueBegin);
				String value = message.substring(valueBegin, valueEnd);
				message = getText(invocation, "重复的属性", locale, value);	
			} else {
				message = getText(invocation, "errors.db." + status, locale, message);
			}
		} else {
			message = getText(invocation, "errors.system", locale, ex.getMessage());
		}
		
		addActionError(invocation, message);
		return Action.ERROR;
	}

	private void addActionError(ActionInvocation invocation, String message) {
		ValueStack valueStack = invocation.getInvocationContext().getValueStack();
		CompoundRoot root = valueStack.getRoot();
		Iterator iter = root.iterator();
		while (iter.hasNext()) {
			Object action = iter.next();
			if (action instanceof ValidationAware) {
				ValidationAware validationAware = (ValidationAware) action;
				validationAware.addActionError(message);
				break;
			}
		}
	}
	
	private void addActionMessage(ActionInvocation invocation, String message) {
		ValueStack valueStack = invocation.getInvocationContext().getValueStack();
		CompoundRoot root = valueStack.getRoot();
		Iterator iter = root.iterator();
		while (iter.hasNext()) {
			Object action = iter.next();
			if (action instanceof ValidationAware) {
				ValidationAware validationAware = (ValidationAware) action;
				validationAware.addActionMessage(message);
				break;
			}
		}
	}

	private String getText(ActionInvocation invocation, String str, Locale locale, Object ... arguments) {
		String [] args = null;
		if (arguments != null) {
			args = new String[arguments.length];
			int i = 0;
			for (Object argument : arguments) {
				args[i++] = String.valueOf(argument);
			}
		}
		
		ValueStack valueStack = invocation.getInvocationContext().getValueStack();
		CompoundRoot root = valueStack.getRoot();
		Iterator iter = root.iterator();
		while (iter.hasNext()) {
			Object action = iter.next();
			if (action instanceof TextProvider) {
				TextProvider textProvider = (TextProvider) action;
				return textProvider.getText(str, str, args);
			}
		}
		return str;
	}
	
	///////////////////////////////////////////////////////////////////////////
	protected String callOrigExceptionInterceptor(ActionInvocation invocation, Exception e) throws Exception {
		String result;
		if (logEnabled) {
            handleLogging(e);
        }
        List exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings();
        String mappedResult = this.findResultFromException(exceptionMappings, e);
        if (mappedResult != null) {
            result = mappedResult;
            publishException(invocation, new ExceptionHolder(e));
        } else {
            throw e;
        }
        return result;
	}
	
	private String findResultFromException(List exceptionMappings, Throwable t) {
        String result = null;

        // Check for specific exception mappings.
        if (exceptionMappings != null) {
            int deepest = Integer.MAX_VALUE;
            for (Iterator iter = exceptionMappings.iterator(); iter.hasNext();) {
                ExceptionMappingConfig exceptionMappingConfig = (ExceptionMappingConfig) iter.next();
                int depth = getDepth(exceptionMappingConfig.getExceptionClassName(), t);
                if (depth >= 0 && depth < deepest) {
                    deepest = depth;
                    result = exceptionMappingConfig.getResult();
                }
            }
        }

        return result;
    }

	@Override
	public void setServletResponse(HttpServletResponse response) {
	}
}